package utils.swtbot;

import java.util.List;

import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTableItem;
import org.junit.Assert;

/**
 * The сlass that encapsulates UI testing Bot with it`s default preferences.
 * 
 * @author <a href="mailto:Daniil.Yaroslavtsev@gmail.com">Daniil Yaroslavtsev</a>
 */
public class UITest extends Assert {

    /** The Bot for UI testing. */
    private static final SWTWorkbenchBot BOT = new SWTWorkbenchBot();

    /**
     * The default tests playback delay in ms. Changes an execution speed of SWTBot tests. System default is 0.
     * */
    private static int defaultPlaybackDelay = 0;

    /**
     * The default timeout before SWTBot tests execution.
     * */
    private static int timeBeforeTestsTimeout = 0;

    /** The timeout between two tests execution in ms. */
    private static int defaultBetweenTestsTimaout = 0;

    /**
     * Gets the bot.
     * 
     * @return The current UI testing bot.
     */
    protected static SWTWorkbenchBot getBot() {
        return BOT;
    }

    protected static int getDefaultTestsPlaybackDelay() {
        return defaultPlaybackDelay;
    }

    /**
     * Setups the necessary bot parameters.
     */
    protected static void setupSWTBotParams() {

        // Set timeout for execution of SWTBot tests. System default is 0.
        SWTBotPreferences.TIMEOUT = timeBeforeTestsTimeout;

        // Set execution speed of SWTBot tests. System default is 0.
        setTestsPlaybackDelay(defaultPlaybackDelay);

    }

    /**
     * Sets the tests playback delay in ms.
     * 
     * @param milliseconds
     *            - the new tests playback delay.
     */
    protected static void setTestsPlaybackDelay(final int milliseconds) {
        SWTBotPreferences.PLAYBACK_DELAY = milliseconds;
    }

    /**
     * Causes the bot to sleep during default time between tests
     */
    protected static void doSleepBetweenTests() {
        BOT.sleep(defaultBetweenTestsTimaout);
    }

    /**
     * Closes all unnecessary editors, shells and all views except "Log4j-Viewer".
     */
    protected static void closeUnnecessaryViewsIfNeeded() {

        BOT.closeAllEditors();
        BOT.closeAllShells();

        // close all views except "Log4j-Viewer".
        List<SWTBotView> curOpenedViewsList = BOT.views();

        for (SWTBotView view : curOpenedViewsList) {
            String curViewTitle = view.getTitle();
            if (curViewTitle.equals("Log4j-Viewer")) {
                // no code
            } else {
                view.close();
            }
        }
    }

    /**
     * Gets a KeyStroke for the key defined by the keyCode.
     * 
     * @param keyCode
     *            - any SWT keyCode.
     * @return The new KeyStroke instance for the specified key.
     */
    protected KeyStroke getKey(final int keyCode) {
        return KeyStroke.getInstance(keyCode);
    }

    /**
     * Writes the text to the specified cell of parent table in loverCase letters by alphanumeric keys pressing.
     * 
     * @param table
     *            - the parent table.
     * @param row
     *            - the row number.
     * @param column
     *            - the column number.
     * @param text
     *            - the alphanumeric text.
     * @param editorActivationType
     *            - the editor activation type.
     * @throws ParseException
     *             if the current keyCode is not an alphanumeric key code.
     */
    protected void writeToCell(final TableViewer viewer, final int row, final int column, final String text,
            final int editorActivationType) throws ParseException {

        final SWTBotTable table = new SWTBotTable(viewer.getTable());

        table.select(row);

        SWTBotTableItem item = table.getTableItem(row);

        startEditing(viewer, row, column, editorActivationType);

        for (char keyChar : text.toLowerCase().toCharArray()) {
            if (isAlphanumericKey(keyChar)) {
                item.pressShortcut(getKey(keyChar));
            } else {
                throw new ParseException("Can`t parse key: \"" + keyChar + "\" - it is not an alphanumeric key!");
            }
        }
    }

    /**
     * Checks that specified number is a code of SWT alphanumeric key (lovercase keys only).
     * 
     * @param keyCode
     *            - SWT key code.
     * @return true, if specified number is a code of alphanumeric key.
     */
    private boolean isAlphanumericKey(final int keyCode) {
        return ((keyCode >= 48) && (keyCode <= 57)) // 0..9 numbers
                || ((keyCode >= 97) && (keyCode <= 122)); // a - z (loverCase only!)         
    }

    /**
     * Starts the cell editor for the cell specified by row and column number.
     * 
     * @param table
     *            - the parent table.
     * @param row
     *            - the row number.
     * @param column
     *            - the column number.
     * @param editorActivationType
     *            - the editor activation type.
     * @throws IllegalArgumentException
     *             if an invalid editor activation type specified.
     * @see MOUSE_CLICK_ACTIVATION
     */
    protected void startEditing(final TableViewer viewer, final int row, final int column,
            final int editorActivationType) {

        final SWTBotTable table = new SWTBotTable(viewer.getTable());

        switch (editorActivationType) {
        case UITestConstants.MOUSE_CLICK_ACTIVATION:
            table.click(row, column);
            break;
        case UITestConstants.MOUSE_DOUBLE_CLICK_ACTIVATION:
            table.doubleClick(row, column);
            break;
        case UITestConstants.F2_KEY_ACTIVATION:
            table.click(row, column);
            table.pressShortcut(getKey(SWT.F2));
            break;
        default:
            throw new IllegalArgumentException("Can`t support the specified editor activation type.");
        }
        waitForCellEditorState(viewer, true);
    }

    /**
     * Generates a Random integer number within a given range.
     * 
     * @param begin
     *            - the range begin.
     * @param end
     *            - the range end.
     * @return random number that is in the given range.
     */
    protected int randomInt(final int begin, final int end) {
        return (int) (begin + (Math.random() * ((end - begin) + 1)));
    }

    /**
     * Waits until the default timeout is reached or until the TableViewer cell editor`s "isActive()" state becomes
     * equal to the specified state (true/false).
     * 
     * @param tableWiewer
     *            - the TableViewer to retrieve the cell editor state.
     * @param condition
     *            -
     */
    protected void waitForCellEditorState(final TableViewer tableWiewer, final boolean condition) {
        BOT.waitUntil(new CellEditorWaiter(tableWiewer, condition), UITestConstants.DEFAULT_CELL_EDITOR_TIMEOUT);
    }

    /**
     * Waits for UI repaint.
     */
    protected void waitForUIRepaint() {
        BOT.sleep(100);
    }

    /**
     * Causes the bot to wait.
     * 
     * @param millis
     *            - wait time in milliseconds.
     * */
    protected void sleep(final int millis) {
        BOT.sleep(millis);
    }

    class CellEditorWaiter extends DefaultCondition {

        private final TableViewer tableWiewer;
        private final boolean condition;

        // initialize
        CellEditorWaiter(final TableViewer tableWiewer, final boolean condition) {
            this.tableWiewer = tableWiewer;
            this.condition = condition;
        }

        // return true if the condition matches, false otherwise
        @Override
        public boolean test() {
            return tableWiewer.isCellEditorActive() == condition;
        }

        // provide a human readable error message
        @Override
        public String getFailureMessage() {
            return "Timed out while waiting for the cell editor.";
        }
    }

}
